/*
 * Copyright (C) 2016  Nicola Spanti (RyDroid) <dev@nicola-spanti.info>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */


#include "PlanetWars2dRT-core/utils/observers.h"
#include <string.h>


EXTERN_C_BEGIN


void
observers_init_first_time_only(observers_t* observers)
{
  VECTOR_GENERIC_STATIC_INIT_FIRST_TIME_ONLY(observers, 0,
					     observer_t);
}

void
observers_destructor(observers_t* observers)
{
  observer_t* current = VECTOR_GENERIC_STATIC_GET_BEGIN_POINTER(observers);
  const observer_t* end = VECTOR_GENERIC_STATIC_GET_END_POINTER(observers);
  while(current != end)
    {
      observer_destructor_unsafe(current);
    }
  VECTOR_GENERIC_STATIC_DESTRUCTOR(observers);
}

size_t
observers_get_nb_unsafe(const observers_t* observers)
{
  return VECTOR_GENERIC_STATIC_GET_SIZE_UNSAFE(observers);
}

size_t
observers_get_nb(const observers_t* observers)
{
  return VECTOR_GENERIC_STATIC_GET_SIZE(observers);
}

bool
observers_add(observers_t* observers,
	      const observer_t* an_observer)
{
  if(observers == NULL || observer_is_null(an_observer))
    {
      return false;
    }
  VECTOR_GENERIC_STATIC_PUSH_BACK(observers,
				  *an_observer,
				  observer_t);
  return true;
}

static inline
observer_t*
observers_get_pointer_unsafe(const observers_t* observers,
			     const observer_t*  an_observer)
{
  observer_t* current = VECTOR_GENERIC_STATIC_GET_BEGIN_POINTER(observers);
  const observer_t* end = VECTOR_GENERIC_STATIC_GET_END_POINTER(observers);
  while(current != end)
    {
      if(observer_equals_unsafe(current, an_observer))
	{
	  return current;
	}
    }
  return NULL;
}

bool
observers_remove_without_order(observers_t*      observers,
			       const observer_t* an_observer)
{
  if(VECTOR_GENERIC_STATIC_IS_EMPTY(observers))
    {
      return false;
    }

  observer_t* ptr = observers_get_pointer_unsafe(observers, an_observer);
  observer_t* end = VECTOR_GENERIC_STATIC_GET_END_POINTER(observers);
  if(ptr >= end)
    {
      return false;
    }
  
  observer_destructor_unsafe(ptr);
  *(end -2) = *ptr;
  VECTOR_GENERIC_STATIC_POP_BACK(observers);
  return true;
}

bool
observers_remove_with_order(observers_t*      observers,
			    const observer_t* an_observer)
{
  if(VECTOR_GENERIC_STATIC_IS_EMPTY(observers))
    {
      return false;
    }

  observer_t* ptr = observers_get_pointer_unsafe(observers, an_observer);
  observer_t* end = VECTOR_GENERIC_STATIC_GET_END_POINTER(observers);
  if(ptr >= end)
    {
      return false;
    }
  
  observer_destructor_unsafe(ptr);
  memmove(ptr, ptr+1, end - ptr - 1);
  VECTOR_GENERIC_STATIC_POP_BACK(observers);
  return true;
}

void
observers_clear(observers_t* observers)
{
  if(observers != NULL)
    {
      VECTOR_GENERIC_STATIC_CLEAR(observers);
    }
}

void
observers_notify(observers_t* observers,
		 void* an_observable,
		 void* data)
{
  if(observers != NULL || an_observable != NULL)
    {
      observer_t* current = VECTOR_GENERIC_STATIC_GET_BEGIN_POINTER(observers);
      const observer_t* end = VECTOR_GENERIC_STATIC_GET_END_POINTER(observers);
      while(current != end)
	{
	  if(current->handler != NULL)
	    {
	      current->handler(an_observable, current->data, data);
	    }
	  ++current;
	}
    }
}


EXTERN_C_END
